package com.mybatis.jpa.plugin;

import com.baomidou.mybatisplus.annotation.TableId;
import com.mybatis.jpa.common.PersistentUtil;
import com.mybatis.jpa.util.ColumnMetaResolver;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author svili
 */
public class ResultMapSwapper {

  private Configuration configuration;

  /**
   * Result Maps collection,key : id
   */
  private ConcurrentHashMap<String, ResultMap> resultMaps = new ConcurrentHashMap<>();

  public ResultMapSwapper(Configuration configuration) {
    this.configuration = configuration;
  }

  public ResultMap reloadResultMap(String resource, String id, Class<?> type) {
    if (!resultMaps.containsKey(id)) {
      resultMaps.put(id, resolveResultMap(resource, id, type));
    }

    return resultMaps.get(id);
  }

  public void registerResultMap(ResultMap resultMap) {
    configuration.addResultMap(resultMap);
  }

  /**
   * 解析 ResultMap
   * @param resource
   * @param id
   * @param type
   * @return
   */
  public ResultMap resolveResultMap(String resource, String id, Class<?> type) {
    //先解析ResultMapping
    List<ResultMapping> resultMappings = resolveResultMappings(resource, id, type);
    //然后根据ResultMapping构建出ResultMap
    return new ResultMap.Builder(configuration, id, type, resultMappings).build();
  }

  //根据参数构造出ResultMapping集合
  public List<ResultMapping> resolveResultMappings(String resource, String id, Class<?> type) {
    //创建ResultMapping集合
    List<ResultMapping> resultMappings = new ArrayList<>();

    //获取MapperBuilderAssistant
    MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, resource);

    //获取jpa注解字段
    List<Field> fields = PersistentUtil.getPersistentFieldsV2(type);


    for (Field field : fields) {
      //获取java属性名
      String property = field.getName();
      //获取数据库字段名
      String column = PersistentUtil
          .getColumnName(field, configuration.isMapUnderscoreToCamelCase());
      //获取java属性的类型
      Class<?> javaType = field.getType();

      //resultMap is not need jdbcType
      JdbcType jdbcType = null;

      //嵌套
      String nestedSelect = null;
      String nestedResultMap = null;

      //判断是否为级联属性
      if (PersistentUtil.isAssociationField(field)) {
        // OneToOne or OneToMany

        // mappedBy
        //获取注解中定义关联字段的字段名
        column = PersistentUtil.getMappedName(field);
        if (field.isAnnotationPresent(OneToOne.class)) {
          nestedResultMap = id + "_association[" + javaType.getSimpleName() + "]";
          registerResultMap(resolveResultMap(resource, nestedResultMap, javaType));
        }
        if (field.isAnnotationPresent(OneToMany.class)) {
          Type genericType = field.getGenericType();
          if (genericType instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericType;
            Class<?> actualType = (Class<?>) pt.getActualTypeArguments()[0];
            // create resultMap with actualType
            nestedResultMap = id + "collection[" + actualType.getSimpleName() + "]";
            registerResultMap(resolveResultMap(resource, nestedResultMap, actualType));
          }
        }
      }

      String notNullColumn = null;
      String columnPrefix = null;
      String resultSet = null;
      String foreignColumn = null;
      // if primaryKey,then flags.add(ResultFlag.ID);
      List<ResultFlag> flags = new ArrayList<>();
      if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(TableId.class)) {
        flags.add(ResultFlag.ID);
      }
      // lazy or eager
      boolean lazy = false;
      // typehandler
      Class<? extends TypeHandler<?>> typeHandlerClass = ColumnMetaResolver
          .resolveTypeHandler(field);

      ResultMapping resultMapping = assistant.buildResultMapping(type, property, column,
          javaType, jdbcType, nestedSelect, nestedResultMap, notNullColumn, columnPrefix,
          typeHandlerClass, flags, resultSet, foreignColumn, lazy);
      resultMappings.add(resultMapping);
    }
    return resultMappings;

  }

}
